﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using MyGIS;
using System.Windows.Forms;
using System.Data.OleDb;
using static MyGIS.GISShapefile;
using System.Text.RegularExpressions;
using System.Data;

namespace Midterm_test
{
    class ShapefileConverter
    {

        private struct FileHeader//文件头
        {
            public int FileCode;
            public int Unused1;
            public int Unused2;
            public int Unused3;
            public int Unused4;
            public int Unused5;
            public int FileLength;
            public int Version;
            public int ShapeType;
            public double XMin;
            public double YMin;
            public double XMax;
            public double YMax;
            public double ZMin;
            public double ZMax;
            public double MMin;
            public double MMax;
        }


        private struct RecordHeader//记录头
        {
            public int RecordNumber;
            public int ContentLength;
            public int ShapeType;
        }


        private enum ShapeType : int//数据类型枚举
        {
            NullShape = 0,
            Point = 1,
            Polyline = 3,
            Polygon = 5,
            MultiPoint = 8,
            PointZ = 11,
            PolylineZ = 13,
            PolygonZ = 15,
            MultiPointZ = 18,
            PointM = 21,
            PolylineM = 23,
            PolygonM = 25,
            MultiPointM = 28,
            MultiPatch = 31
        }




        /// <summary>
        /// Shapefile转Txt
        /// </summary>
        /// <param name="shapefilePath"></param>
        /// <param name="textFilePath"></param>
        public static void ConvertToText(GISLayer layer, string shapefilePath, string textFilePath)
        {
            FileHeader fileHeader = new FileHeader();
            FileStream shapefileStream = new FileStream(shapefilePath, FileMode.Open);
            BinaryReader shapefileReader = new BinaryReader(shapefileStream);
            StreamWriter textFileWriter = new StreamWriter(textFilePath);
            using (shapefileStream)
            {

                // 读取文件头
                fileHeader.FileCode = shapefileReader.ReadInt32();
                fileHeader.Unused1 = shapefileReader.ReadInt32();
                fileHeader.Unused2 = shapefileReader.ReadInt32();
                fileHeader.Unused3 = shapefileReader.ReadInt32();
                fileHeader.Unused4 = shapefileReader.ReadInt32();
                fileHeader.Unused5 = shapefileReader.ReadInt32();
                fileHeader.FileLength = shapefileReader.ReadInt32();
                fileHeader.Version = shapefileReader.ReadInt32();
                fileHeader.ShapeType = shapefileReader.ReadInt32();
                fileHeader.XMin = shapefileReader.ReadDouble();
                fileHeader.YMin = shapefileReader.ReadDouble();
                fileHeader.XMax = shapefileReader.ReadDouble();
                fileHeader.YMax = shapefileReader.ReadDouble();
                //在二维shapefile文件中以下4个坐标无效
                fileHeader.ZMin = shapefileReader.ReadDouble();
                fileHeader.ZMax = shapefileReader.ReadDouble();
                fileHeader.MMin = shapefileReader.ReadDouble();
                fileHeader.MMax = shapefileReader.ReadDouble();

                textFileWriter.WriteLine("File Header:");
                textFileWriter.WriteLine("File Code: {0}", fileHeader.FileCode);
                textFileWriter.WriteLine("File Length: {0}", fileHeader.FileLength);
                textFileWriter.WriteLine("Version:{0}", fileHeader.Version);
                textFileWriter.Write("Shape Type: ");
                switch ((ShapeType)fileHeader.ShapeType)
                {
                    case ShapeType.NullShape:
                        textFileWriter.WriteLine("Null Shape\t0");
                        break;
                    case ShapeType.Point:
                        textFileWriter.WriteLine("Point\t1");
                        break;
                    case ShapeType.Polyline:
                        textFileWriter.WriteLine("Polyline\t3");
                        break;
                    case ShapeType.Polygon:
                        textFileWriter.WriteLine("Polygon\t5");
                        break;
                    case ShapeType.MultiPoint:
                        textFileWriter.WriteLine("MultiPoint");
                        break;
                    case ShapeType.PointZ:
                        textFileWriter.WriteLine("PointZ");
                        break;
                    case ShapeType.PolylineZ:
                        textFileWriter.WriteLine("PolylineZ");
                        break;
                    case ShapeType.PolygonZ:
                        textFileWriter.WriteLine("PolygonZ");
                        break;
                    case ShapeType.MultiPointZ:
                        textFileWriter.WriteLine("MultiPointZ");
                        break;
                    case ShapeType.PointM:
                        textFileWriter.WriteLine("PointM");
                        break;
                    case ShapeType.PolylineM:
                        textFileWriter.WriteLine("PolylineM");
                        break;
                    case ShapeType.PolygonM:
                        textFileWriter.WriteLine("PolygonM");
                        break;
                    case ShapeType.MultiPointM:
                        textFileWriter.WriteLine("MultiPointM");
                        break;
                    case ShapeType.MultiPatch:
                        textFileWriter.WriteLine("MultiPatch");
                        break;
                    default:
                        textFileWriter.WriteLine("Unknown");
                        break;
                }
                //写入坐标范围
                textFileWriter.WriteLine("XMin: {0}", fileHeader.XMin);
                textFileWriter.WriteLine("YMin: {0}", fileHeader.YMin);
                textFileWriter.WriteLine("XMax: {0}", fileHeader.XMax);
                textFileWriter.WriteLine("YMax: {0}", fileHeader.YMax);
                textFileWriter.WriteLine("ZMin: {0}", fileHeader.ZMin);
                textFileWriter.WriteLine("ZMax: {0}", fileHeader.ZMax);
                textFileWriter.WriteLine("MMin: {0}", fileHeader.MMin);
                textFileWriter.WriteLine("MMax: {0}", fileHeader.MMax);
                textFileWriter.WriteLine("----------------------------------------------------------");


                //(已解决)bug:System.ObjectDisposedException:“无法访问已关闭的文件”
                //原因：using (shapefileStream)语句的大括号范围错误，导致shapefileStream过早关闭
                while (shapefileStream.Position < shapefileStream.Length)
                {
                    // 读取记录头
                    RecordHeader recordHeader = new RecordHeader();
                    recordHeader.RecordNumber = GISShapefile.FromBigToLittle(shapefileReader.ReadInt32());
                    recordHeader.ContentLength = GISShapefile.FromBigToLittle(shapefileReader.ReadInt32()) * 2 - 4;
                    shapefileReader.ReadInt32();

                    // 读取记录内容
                    byte[] recordContent = shapefileReader.ReadBytes(recordHeader.ContentLength);

                    // 将记录内容转换为文本格式
                    string recordText = ConvertRecordToText(recordContent, recordHeader.RecordNumber, fileHeader.ShapeType);

                    // 写入记录文本到文本文件中
                    textFileWriter.WriteLine(recordText);
                }
            }
            WriteShapefileAttributesToText(textFileWriter, shapefilePath, textFilePath);//写入属性数据
            textFileWriter.Close();
            shapefileReader.Close();
        }



        /// <summary>
        /// 将Shapefile记录内容转换为文本格式
        /// </summary>
        /// <param name="recordContent"></param>
        /// <returns></returns>
        private static string ConvertRecordToText(byte[] recordContent, int recordNumber, int shapeType)
        {

            // 根据Shape类型转换记录内容为文本格式
            //如果是点
            if ((ShapeType)shapeType == ShapeType.Point || (ShapeType)shapeType == ShapeType.PointZ || (ShapeType)shapeType == ShapeType.PointM)
            {
                double x = BitConverter.ToDouble(recordContent, 0);
                double y = BitConverter.ToDouble(recordContent, 8);
                string recordText = string.Format("{0}\t{1}\t{2}\t", recordNumber, x, y);
                return recordText;
            }
            // 如果是多点
            if ((ShapeType)shapeType == ShapeType.MultiPoint ||
                (ShapeType)shapeType == ShapeType.MultiPointZ ||
                (ShapeType)shapeType == ShapeType.MultiPointM)
            {
                int numPoints = BitConverter.ToInt32(recordContent, 32);//点集中的点数
                double xMin = BitConverter.ToDouble(recordContent, 0);
                double yMin = BitConverter.ToDouble(recordContent, 8);
                double xMax = BitConverter.ToDouble(recordContent, 16);
                double yMax = BitConverter.ToDouble(recordContent, 24);

                StringBuilder sb = new StringBuilder();//实例化可变格式字符串，便于将接下来读到的点添加到字符串中
                sb.AppendFormat("{0}\t{1}\t{2}\t{3}\t{4}\t{5}", recordNumber, numPoints, xMin, yMin, xMax, yMax);
                int offset = 36;
                for (int i = 0; i < numPoints; i++)
                {
                    double x = BitConverter.ToDouble(recordContent, offset);
                    double y = BitConverter.ToDouble(recordContent, offset + 8);
                    sb.AppendFormat("\tx:{0}\ty:{1}", x, y);
                    offset += 16;//向前提升16个字节，以读取下一组x和y
                }
                string recordText = sb.ToString();
                return recordText;
            }

            // 如果是折线或面类型的记录
            if ((ShapeType)shapeType == ShapeType.Polyline ||
                (ShapeType)shapeType == ShapeType.PolylineZ ||
                (ShapeType)shapeType == ShapeType.PolylineM ||
                (ShapeType)shapeType == ShapeType.Polygon ||
                (ShapeType)shapeType == ShapeType.PolygonZ ||
                (ShapeType)shapeType == ShapeType.PolygonM)
            {
                int numParts = BitConverter.ToInt32(recordContent, 32);
                int numPoints = BitConverter.ToInt32(recordContent, 36);
                double xMin = BitConverter.ToDouble(recordContent, 0);
                double yMin = BitConverter.ToDouble(recordContent, 8);
                double xMax = BitConverter.ToDouble(recordContent, 16);
                double yMax = BitConverter.ToDouble(recordContent, 24);

                StringBuilder sb = new StringBuilder();
                sb.AppendFormat("RecordHeader: recordNumber, numParts, numPoints, XMin, YMin, XMax, YMax\n");
                sb.AppendFormat("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}", recordNumber, numParts, numPoints, xMin, yMin, xMax, yMax);
                sb.AppendFormat("\n--------------------------------------------------------------\n");
                int offset = 40;
                for (int i = 0; i < numParts; i++)//读取面实体中每个面的起始节点位置
                {
                    int partIndex = BitConverter.ToInt32(recordContent, offset);
                    //sb.AppendFormat("\tpartIndex:{0}\n", partIndex);
                    offset += 4;
                }

                for (int i = 0; i < numPoints; i++)//读取每个节点的x和y坐标
                {
                    double x = BitConverter.ToDouble(recordContent, offset);
                    double y = BitConverter.ToDouble(recordContent, offset + 8);
                    sb.AppendFormat("\t{0}\t{1}\n", x, y);
                    offset += 16;
                }
                string recordText = sb.ToString();
                return recordText;
            }

            // 如果是未知类型的记录
            return string.Format("{0}\tUnknown Shape Type", recordNumber);
        }





        /// <summary>
        /// 将DBF文件中的属性数据写入到文本文件
        /// </summary>
        /// <param name="shapefilePath"></param>
        /// <param name="textFilePath"></param>
        public static void WriteShapefileAttributesToText(StreamWriter writer, string shapefilePath, string textFilePath)
        {
            string dbfFilePath = Path.ChangeExtension(shapefilePath, ".dbf");//更改扩展名

            // 连接到 DBF 文件
            using (OleDbConnection connection = new OleDbConnection($"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={Path.GetDirectoryName(dbfFilePath)};Extended Properties=dBASE IV;"))
            {
                connection.Open();

                // 查询属性数据
                using (OleDbCommand command = new OleDbCommand($"SELECT * FROM {Path.GetFileName(dbfFilePath)}", connection))
                using (OleDbDataReader reader = command.ExecuteReader())
                using (writer)
                {
                    writer.WriteLine("\n-----------------------------------");
                    writer.WriteLine("\nAttribute data:");
                    // 写入属性列标题
                    for (int i = 0; i < reader.FieldCount; i++)
                    {
                        writer.Write(reader.GetName(i));
                        if (i < reader.FieldCount - 1)
                        {
                            writer.Write("\t");
                        }
                    }
                    writer.WriteLine();



                    while (reader.Read())
                    {


                        // 逐行读取属性数据并写入文本文件
                        for (int i = 0; i < reader.FieldCount; i++)
                        {
                            object value = reader.GetValue(i);
                            writer.Write(value?.ToString() ?? "");
                            if (i < reader.FieldCount - 1)
                            {
                                writer.Write("\t");
                            }
                        }
                        writer.WriteLine();
                    }
                }
            }
        }
        //（已解决）WriteShapefileAttributesToText函数bug：调用此函数后属性数据会覆盖之前被写入的空间信息数据。
        //原因：在该函数中重新实例化了一个StreamWriter，造成之前的StreamWriter被覆盖。
        //解决方法：让WriteShapefileAttributesToText作为函数被ConvertToText调用，让StreamWriter作为参数传入，而不是实例化一个新的。





        /// <summary>
        /// 从txt文档中读取空间和属性数据
        /// </summary>
        /// <param name="textFilePath"></param>
        /// <returns></returns>
        public static GISLayer ReadTxt(string txtFilePath)
        {
            string[] lines = File.ReadAllLines(txtFilePath);
            List<GISField> fields = null;


            // 解析文件头信息
            string[] headerInfo = new string[12];//用于存储文件头的数字信息
            for (int i = 0; i < 12; i++)
            {
                string line = lines[i];
                // 使用正则表达式提取数字部分
                Match match = Regex.Match(line, @"[\d.]+");
                if (match.Success)
                {
                    headerInfo[i] = match.Value;
                }
            }
            SHAPETYPE shapeType = (SHAPETYPE)Enum.Parse(typeof(SHAPETYPE), headerInfo[4]); // 获取空间对象类型
            double xMin = Convert.ToDouble(headerInfo[5]);
            double yMin = Convert.ToDouble(headerInfo[6]);
            double xMax = Convert.ToDouble(headerInfo[7]);
            double yMax = Convert.ToDouble(headerInfo[8]);

            //解析记录头,获取空间对象的数量
            int recordCount = 0;
            if (shapeType == SHAPETYPE.poloygon || shapeType == SHAPETYPE.line)
            {
                recordCount = GetPoloyRecordCount(txtFilePath);
            }
            else if (shapeType == SHAPETYPE.point)
            {
                recordCount = getPointRecordCount(txtFilePath);
            }




            // 解析空间数据
            List<GISFeature> features = new List<GISFeature>();
            DataTable table = ReadTxtFields(txtFilePath, recordCount);

            // 创建 GISLayer 对象
            GISLayer gisLayer = new GISLayer(txtFilePath, shapeType,
                new GISExtent(xMin, xMax, yMin, yMax), ReadFields(table));

            //读取txt点数据
            if (shapeType == SHAPETYPE.point)
            {
                int rowIndex = 0;
                int currentLineNumber = 0;
                using (StreamReader sr = new StreamReader(txtFilePath))
                {
                    while (!sr.EndOfStream)//跳转到15行，跳过文件头
                    {
                        currentLineNumber++;
                        string line = sr.ReadLine();
                        if (currentLineNumber == 14)
                        {
                            break;
                        }
                    }
                    for (int i = 0; i < recordCount; i++)
                    {
                        GISPoint point = ReadTxtPoint(sr);
                        GISFeature onefeature = new GISFeature(point, ReadAttribute(table, rowIndex));
                        gisLayer.AddFeature(onefeature);
                        rowIndex++;
                    }
                }
            }

            //读取txt线数据
            else if (shapeType == SHAPETYPE.line)
            {
                int rowIndex = 0;
                int currentLineNumber = 0;
                using (StreamReader sr = new StreamReader(txtFilePath))
                {
                    while (!sr.EndOfStream)//跳转到15行，跳过文件头
                    {
                        currentLineNumber++;
                        string line = sr.ReadLine();
                        if (currentLineNumber == 14)
                        {
                            break;
                        }
                    }
                    for (int i = 0; i < recordCount; i++)
                    {
                        int vertexNumber = ReadTxtVertexCount(sr);
                        GISLine gisLine = ReadTxtLine(sr, vertexNumber);
                        GISFeature onefeature = new GISFeature(gisLine, ReadAttribute(table, rowIndex));
                        gisLayer.AddFeature(onefeature);
                        rowIndex++;
                    }
                }
            }



            else if (shapeType == SHAPETYPE.poloygon)
            {
                int rowIndex = 0;
                int currentLineNumber = 0;
                using (StreamReader sr = new StreamReader(txtFilePath))
                {
                    while (!sr.EndOfStream)//跳转到15行，跳过文件头
                    {
                        currentLineNumber++;
                        string line = sr.ReadLine();
                        if (currentLineNumber == 14)
                        {
                            break;
                        }
                    }
                    for (int i = 0; i < recordCount; i++)
                    {
                        int vertexNumber = ReadTxtVertexCount(sr);
                        GISPolygon polygon = ReadTxtPolygon(sr, vertexNumber);
                        GISFeature onefeature = new GISFeature(polygon, ReadAttribute(table, rowIndex));
                        gisLayer.AddFeature(onefeature);
                        rowIndex++;
                    }
                }
            }
            return gisLayer;
        }




        /// <summary>
        /// 获取线面txt文件中的记录数
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        static int GetPoloyRecordCount(string filePath)
        {
            // 打开文本文件进行读取
            using (StreamReader reader = new StreamReader(filePath))
            {
                int lineNumber = 1;
                string line;
                int count = 0; // 计数器，用于记录包含特定字符的行数
                // 逐行搜索文本文件
                while ((line = reader.ReadLine()) != null)
                {
                    // 检查当前行是否包含目标字符
                    if (line.Contains("-----"))
                    {
                        count++;
                    }
                    lineNumber++;
                }
                return count - 2;
            }
        }



        /// <summary>
        /// 获取点txt文件中的记录数
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        static int getPointRecordCount(string filePath)
        {
            bool startReading = false;//指示是否开始读取
            // 打开文本文件进行读取
            using (StreamReader reader = new StreamReader(filePath))
            {
                int lineNumber = 1;
                string line;
                int count = 0; // 计数器，用于记录点实体的数量
                // 逐行搜索文本文件
                while ((line = reader.ReadLine()) != null)
                {
                    // 检查当前行是否包含目标字符
                    if (line.Contains("------") && startReading == false)
                    {
                        startReading = true;
                        continue;
                    }
                    if (startReading == true)
                    {
                        count++;
                    }

                    if (startReading == true && string.IsNullOrWhiteSpace(line))
                    {
                        break;
                    }

                    lineNumber++;
                }
                return count - 2;
            }
        }




        /// <summary>
        /// 读取txt文件中的点数据
        /// </summary>
        /// <param name="sr"></param>
        /// <returns></returns>
        static GISPoint ReadTxtPoint(StreamReader sr)
        {
            string[] lineRecord = sr.ReadLine().Split('\t');
            double x = Convert.ToDouble(lineRecord[1]);
            double y = Convert.ToDouble(lineRecord[2]);
            return new GISPoint(new GISVertex(x, y));
        }



        /// <summary>
        /// 读取txt文件中的线数据
        /// </summary>
        /// <param name="sr"></param>
        /// <param name="vertexNumber"></param>
        /// <returns></returns>
        static GISLine ReadTxtLine(StreamReader sr, int vertexNumber)
        {
            List<GISVertex> vertexs = new List<GISVertex>();
            sr.ReadLine();
            for (int i = 0; ; i++)
            {
                string oneline = sr.ReadLine();
                if(!string.IsNullOrWhiteSpace(oneline))//当未读到空行时
                {
                    string[] lineRecord = oneline.Split('\t');
                    double x = Convert.ToDouble(lineRecord[1]);
                    double y = Convert.ToDouble(lineRecord[2]);
                    GISVertex oneVertex = new GISVertex(x, y);
                    vertexs.Add(oneVertex);
                }
                else
                {
                    break;
                }
            }
            GISLine line = new GISLine(vertexs);
            return line;
        }



        /// <summary>
        /// 读取txt文件中的面数据
        /// </summary>
        /// <param name="sr"></param>
        /// <param name="vertexNumber"></param>
        /// <returns></returns>
        static GISPolygon ReadTxtPolygon(StreamReader sr, int vertexNumber)
        {
            List<GISVertex> vertexs = new List<GISVertex>();
            sr.ReadLine();
            for (int i = 0; ; i++)
            {
                string oneline = sr.ReadLine();
                if (!string.IsNullOrWhiteSpace(oneline))//当未读到空行时
                {
                    string[] lineRecord = oneline.Split('\t');
                    double x = Convert.ToDouble(lineRecord[1]);
                    double y = Convert.ToDouble(lineRecord[2]);
                    GISVertex oneVertex = new GISVertex(x, y);
                    vertexs.Add(oneVertex);
                }
                else
                {
                    break;
                }
             }
            GISPolygon polygon = new GISPolygon(vertexs);
            return polygon;
        }



        /// <summary>
        /// 获取各空间实体所包含的节点数
        /// </summary>
        /// <param name="sr"></param>
        /// <returns></returns>
        static int ReadTxtVertexCount(StreamReader sr)
        {
            sr.ReadLine();
            int vertexCount = 0;
            string[] recordHeader = sr.ReadLine().Split('\t');
            vertexCount = Convert.ToInt32(recordHeader[2]);
            return vertexCount;
        }


        /// <summary>
        /// 读取属性数据,返回值为datatable
        /// </summary>
        /// <param name="AttributeValue"></param>
        /// <param name="FieldCount"></param>
        /// <returns></returns>
        static DataTable ReadTxtFields(string filePath, int FieldCount)
        {
            List<GISField> fields = new List<GISField>();
            DataTable dataTable = new DataTable();
            using (StreamReader reader = new StreamReader(filePath))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    if (line.Contains("Attribute data"))
                    {
                        string[] headers = reader.ReadLine().Split('\t');
                        foreach (string header in headers)
                        {
                            dataTable.Columns.Add(header);
                        }
                        while (!reader.EndOfStream)
                        {
                            string[] rows = reader.ReadLine().Split('\t');
                            DataRow dataRow = dataTable.NewRow();
                            for (int i = 0; i < headers.Length; i++)
                            {
                                dataRow[i] = rows[i];
                            }
                            dataTable.Rows.Add(dataRow);
                        }
                    }
                }
            }
            return dataTable;
        }
    }
}


